package org.example.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.example.common.Constants;
import org.example.common.LoginVo;
import org.example.common.R;
import org.example.common.StatusCode;
import org.example.utils.JWTUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * JWT授权过滤器
 * <p>
 * 核心作用：
 * 1. 从请求头中获取JWT。
 * 2. 解析JWT，提取用户信息和权限信息。
 * 3. 将用户信息和权限封装到Spring Security的Authentication对象中。
 * 4. 将Authentication对象存入SecurityContext，以便后续的权限校验。
 * </p>
 */
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    /**
     * 过滤器的核心逻辑
     *
     * @param request     请求对象
     * @param response    响应对象
     * @param filterChain 过滤器链
     * @throws ServletException 如果发生Servlet异常
     * @throws IOException      如果发生IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 1. 从请求头中获取JWT，约定的头字段为Constants.TOKEN
        String tokenStr = request.getHeader(Constants.TOKEN);

        // 2. 判断Token是否存在
        if (StringUtils.isBlank(tokenStr)) {
            // 如果Token为空，可能是用户未登录，直接放行请求
            // 放行后，用户可能会被Spring Security拦截，因为没有生成Authentication对象
            filterChain.doFilter(request, response);
            return;
        }

        // 3. 解析JWT，提取用户信息和权限信息
        try {
            // 调用JWT工具类解析Token，返回LoginVo对象
            LoginVo loginVo = JWTUtils.parseToken(tokenStr);

            // 如果Token解析失败，返回登录超时的错误信息
            if (loginVo == null) {
                handleUnauthorizedResponse(response, StatusCode.LOGIN_TIMEOUT);
                return;
            }

            // 4. 提取用户信息和权限信息
            // 获取用户名
            String userName = loginVo.getUserName();

            // 获取权限信息，格式为"[ROLE_USER,P8]"，需要去掉前后括号
            String perms = loginVo.getPermission();
            List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(
                    StringUtils.strip(perms, "[]"));

            // 5. 将用户信息和权限封装到Authentication对象中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    loginVo, null, authorityList);

            // 6. 将Authentication对象存入SecurityContext
            // 这样，线程无论走到哪里，都可以通过SecurityContext获取用户信息和权限
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            // 7. 放行请求
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            // 捕获解析Token时的异常，返回登录超时的错误信息
            handleUnauthorizedResponse(response, StatusCode.LOGIN_TIMEOUT);
        }
    }

    /**
     * 处理未授权的响应
     * <p>
     * 当Token解析失败或用户未登录时，返回统一的错误响应。
     * </p>
     *
     * @param response 响应对象
     * @param statusCode 状态码
     * @throws IOException 如果发生IO异常
     */
    private void handleUnauthorizedResponse(HttpServletResponse response, StatusCode statusCode) throws IOException {
        // 创建统一的错误响应对象
        R<Object> result = R.error(statusCode);

        // 设置响应内容类型为JSON
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        // 将错误响应对象序列化为JSON字符串
        String respStr = new ObjectMapper().writeValueAsString(result);

        // 将错误信息写入响应体
        response.getWriter().write(respStr);
    }
}